import json
import random

from io import BytesIO

from django.conf import settings
from django.http import JsonResponse, HttpResponse
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie
from django.contrib.auth import authenticate
from django.db import transaction
from django_redis import get_redis_connection
from django.db import IntegrityError

from gongyequ.decorator import login_required
from gongyequ.models import User
from gongyequ.utils.generate_image_code import picture_code
from gongyequ.utils.validators import is_valid_phone
from lib.tengxunyun.sms import SmsSender
from gongyequ.views.auth_token import create_token
from gongyequ.models import AccessToken


@require_http_methods(['GET'])
@ensure_csrf_cookie
def api_userinfo(request):
    '''获取当前用户信息

    GET /{prefix}/userinfo
    如果用户未登录，返回 401，如果用户已登录，则返回当前账号的信息。
    '''
    if not request.user.is_authenticated:
        return JsonResponse(dict(message='not authorized', status=False, code=401))

    data = dict(message='ok', status=True, code=200)
    data['data'] = request.user.serialize()
    return JsonResponse(data=data, safe=False)


@require_http_methods(['POST'])
def api_register(request):
    '''注册

    POST /{prefix}/register
    Body{
        username=用户名
        password=密码
        password2=确认密码
        identity=手机号
        code=验证码
    }
    '''
    user = _do_register(request)
    data = dict(message='ok', status=True, code=200)
    data['data'] = user.serialize()
    return JsonResponse(data=data)


@transaction.atomic
def _do_register(request):
    data = json.loads(request.body.decode('utf-8'))
    username = data.get('username')
    password = data.get('password')
    password2 = data.get('password2')
    identity = data.get('identity')
    code = data.get('code')

    if is_valid_phone(identity):
        create_args = dict(
            username=username,
            phone=identity,
            phone_validated=True,
        )
    else:
        raise Exception('请输入正确的手机号！！')

    required_fields = ['username', 'password', 'password2', 'code']
    for field in required_fields:
        value = data.get(field, None)
        if not value:
            raise Exception(f'请填写: {field}')
        create_args[field] = value

    if password == password2:
        create_args['password'] = password
    else:
        raise Exception('两次密码不一致！！')

    conn = get_redis_connection('sms_code')
    sms_code = conn.get(identity)
    if sms_code is None and sms_code.decode('utf-8') != code:
        raise Exception('验证码错误！！')

    # 我们不需要保存确认密码和手机验证码，总是忽略
    del create_args['password2'], create_args['code']

    try:
        return User.objects.create_user(**create_args)
    except IntegrityError:
        raise Exception('手机号或用户名,已经存在！')


@require_http_methods(['POST'])
@transaction.atomic
def login(request):
    '''登录
    POST /industrialapi/auth/login
    支持两种认证方式：
        1. 使用 Django 默认的登录机制 session 来保持会话(目前已经启用)
        2. 使用 Token:
                1.所有请求必须携带 token ，后续还有可能增加其他信息(如:app)，这些信息通过 HTTP Header 传递过来
                2.Bearer 传递的 access token ，所有需要登录的接口，都需要提供 access token
                3.RefreshToken 后续可能增加 refresh token 用于刷新 access_token

    校验的具体支持方式，由启用的 AUTHENTICATION BACKEND 决定。
    Body 参数：
    * method: 必填，我们所有的 authentication backend 都要求提供 method
    # 在登录过程中，必须提供 identity, method 两个参数

    # 手机验证码登录
    method: code
    identity: 手机号
    code: 验证码

    # 用户名密码登录
    method: password
    identity: 用户名
    password: 密码
    '''
    data = json.loads(request.body)
    identity = data.get('identity')
    method = data.get('method')

    if method not in ['code', 'password', 'register']:
        return JsonResponse(dict(message='bad usage', status=False, code=400))

    if method == 'code':
        if not is_valid_phone(identity):
            return JsonResponse(dict(message='请输入正确的手机号', status=False, code=400))

        user = User.objects.filter(phone=identity, phone_validated=True).first()
        if not user:
            # 但是这里作为严谨处理还是再判断一下，防止发送过验证码之后用户再修改手机号
            return JsonResponse(dict(message='手机号不存在，请先注册！', status=False, code=400))
        if authenticate(request, **data):
            encoded_jwt = create_token(user.id)
            data = dict(message='ok', status=True, code=200)
            user_data = user.serialize()
            user_data['token'] = encoded_jwt
            data['data'] = user_data
            return JsonResponse(data=data)
        else:
            return JsonResponse(dict(message='bad request', status=False, code=400))

    user = User.objects.filter(username=identity).first()
    if user is not None:
        if authenticate(request, **data):
            encoded_jwt = create_token(user.id)
            data = dict(message='ok', status=True, code=200)
            user_data = user.serialize()
            user_data['token'] = encoded_jwt
            data['data'] = user_data
            return JsonResponse(data=data)
        else:
            return JsonResponse(dict(message='bad request', status=False, code=400))
    else:
        # 这里可能是用户用户名填写错误 或者 是未注册，我们统一返回如下错误！
        return JsonResponse(dict(message='没有此用户，请先注册', status=False, code=400))


@require_http_methods(['POST'])
def send_sms_verify_code(request):
    """发送验证码
    POST /industrialapi/otp/send_code
    Body{
        identity='手机号'
        method='code'
    }
    * 判断该手机号是否注册，如果未注册，给予提示
    * 判断是否已经生成过验证码，如果该手机号已经生成过验证码并且未过期，抛出错误
    * 如果该手机号对应的用户已经被锁定，给予提示
    """
    data = json.loads(request.body.decode('utf-8'))
    identity = data.get('identity')
    method = data.get('method')

    if not method and method not in ['code', 'password', 'register']:
        return JsonResponse(dict(message='bad usage', status=False, code=400))

    if not is_valid_phone(identity):
        return JsonResponse(dict(message='请输入正确的手机号', status=False, code=400))

    if method != 'register':
        user = User.objects.filter(phone=identity, phone_validated=True).first()
        if user:
            if not user.can_authenticate():
                return JsonResponse(dict(message='该用户已经锁定，请联系管理员', status=False, code=400))
        else:
            return JsonResponse(dict(message='请先注册！', status=False, code=400))

    sms_code = _generate_code(identity)
    if not sms_code:
        # 没有生成 code，原因可能有是还在静默期或是其他原因。我们总是给前端返回 200 OK
        return JsonResponse(dict(message='验证码已发送', status=True, code=200))

    try:
        sms = SmsSender()
        sms.send(identity, settings.SMS_TEMPLATE_ID['login'], sms_code)
    except KeyError:
        raise Exception('短信模板 id 不存在')

    # TODO 这里为了方便调试，把验证码也返回，后续上线时会删掉
    return JsonResponse(dict(message='验证码已发送', status=True, code=200, sms_code=sms_code))


def _generate_code(identity):
    """生成验证码"""
    conn = get_redis_connection('sms_code')

    # 看是否已经发送过
    # if conn.get(identity) is not None:
    #     raise Exception('在五分钟之内已经发送过验证码！')

    # 生成验证码并且保存到 redis 中
    sms_code = '%06d' % random.randint(1, 999999)
    conn.set(identity, sms_code, ex=settings.SMS_CODE_EXPIRE)

    return sms_code


@require_http_methods(['POST'])
def check_verify_code(request):
    """校验验证码
    POST /industrialapi/otp/check_code
    Body{
        method='code'  // 登录时：code 注册时：register
        identity='手机号'
        code='验证码'
    }
    """
    data = json.loads(request.body.decode('utf-8'))
    identity = data.get('identity')
    code = data.get('code')
    method = data.get('method')

    if method not in ['code', 'password', 'register']:
        return JsonResponse(dict(message='bad usage', status=False, code=400))

    if not identity or not code:
        return JsonResponse(dict(message='missing identity or code', status=False, code=400))

    conn = get_redis_connection('sms_code')
    sms_code = conn.get(identity)
    if sms_code.decode('utf-8') != code:
        return JsonResponse(dict(message='验证码错误！', status=False, code=400))

    return JsonResponse(dict(message='ok', status=True, code=200))


@require_http_methods(['POST'])
def api_logout(request):
    '''退出

    POST /{prefix}/logout
    '''
    token = request.META.get('HTTP_AUTHORIZATION')
    if not token:
        return JsonResponse(dict(meaasge='missing "Bearer Token" header'))

    try:
        token = token.replace('Bearer ', '')
        token_model = AccessToken.objects.get_token(token=token)
        token_model.revoke()
    except AccessToken.DoesNotExist:
        return JsonResponse(dict(message='bad token', status=False, code=400))

    return JsonResponse(dict(message='ok', status=True, code=200))


@require_http_methods(['GET'])
def image_code(request):
    '''发送图片验证码
    GET /industrialapi/auth/code
    '''
    img, char_code = picture_code()
    stream = BytesIO()
    img.save(stream, format='png')

    conn = get_redis_connection()
    conn.set(char_code, char_code)

    return HttpResponse(stream.getvalue())


@require_http_methods(['POST'])
@login_required()
@transaction.atomic
def reset_password(request):
    """重置密码
    POST /industrialapi/auth/reset_password

    Body{
        new_password='密码'
        confirm_password='密码2'  //确认密码
        phone='手机号'
        code='手机验证码'
    }
    """
    data = json.loads(request.body.decode('utf-8'))
    new_password = data.get('new_password')
    confirm_password = data.get('confirm_password')
    phone = data.get('phone')
    code = data.get('code')

    if is_valid_phone(phone):
        filter_args = dict(phone=phone, phone_validated=True)
    else:
        return JsonResponse(dict(message='请输入正确的手机号', status=False, code=400))

    # 这里为了严谨，不返回错误，防止外部探测账号
    user = User.objects.filter(**filter_args).first()

    require_field = ['new_password', 'confirm_password', 'phone', 'code']
    for field in require_field:
        value = data.get(field)
        if not value:
            return JsonResponse(dict(message=f'{field} 字段必填', status=False, code=400))

    if new_password != confirm_password:
        return JsonResponse(dict(message='两次密码输入不一致，请重新输入', status=False, code=400))

    # 校验验证码
    conn = get_redis_connection('sms_code')
    sms_code = conn.get(phone)
    if sms_code is None:
        raise Exception('验证码不存在，请重新发送')

    if sms_code.decode('utf-8') != code:
        raise Exception('验证码错误')

    if not user:
        # 这里理论上不会出现用户不存在的情况，因为已经发送验证码并校验过了，但仍然以防万一
        # 同时这里返回具有误导性的错误信息
        return JsonResponse(dict(message='验证码错误', status=False, code=400))

    user.set_password(new_password)
    user.save()

    return JsonResponse(dict(message='ok', status=True, code=200))
